#include "dd_filetomesh.h"
#include "dd_vector.h"
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include "dd_dynamic_array.h"

/* THIS FILE NEEDS A LOT OF FIXING, BUT IM BORED SO
 * AS LONG AS IT WORKS, ILL FIX LATER
 */

/* Different functions for each file */
int dd_load_ply(struct dd_mesh *m, const char *path, 
	char mirror, char *attr);
int dd_load_obj(struct dd_mesh *m, const char *path, 
	char mirror, char *attr);

/* Select the right function depending on file_type */
int dd_filetomesh(struct dd_mesh *m, const char *path, 
	char mirror, char *attr, int file_type) {

	switch (file_type) {
		case DD_PLY: return dd_load_ply(m, path, mirror, attr);
		case DD_OBJ: return dd_load_obj(m, path, mirror, attr);
	}

	return -1;
}

/* Parse PLY - STILL WORKING ON IT */
int dd_load_ply(struct dd_mesh *m, const char *path, 
	char mirror, char *attr) {

	(void) attr;
	//Open file and check error
	FILE *f = fopen(path, "r");
	if (!f)
	{
		printf("load_ply: error opening file: %s\n", path);
		return -1;
	}

	//Buffer
	char buff[1024];

	//Data for parsing
	unsigned int vertices = 0, faces = 0;

	/* Vertex positions sorted by index (as they are read)
	 * and by face (what the final array should look like to render the mesh)
	 */
	struct dd_dynamic_array v_pos_index, v_pos_face;
	dd_da_init(&v_pos_index, sizeof(struct dd_vec3));
	dd_da_init(&v_pos_face , sizeof(struct dd_vec3));

	struct dd_dynamic_array v_col_index, v_col_face;
	dd_da_init(&v_col_index, sizeof(struct dd_vec3));
	dd_da_init(&v_col_face , sizeof(struct dd_vec3));

	/* Check ply magic number:
	 *		Get 3 first chars. (ignore new line char)
	 *		Add terminating byte.
	 *		Compare with "ply".
	 */
	if ( fscanf(f, "%3c%*1c", buff) == EOF)
		goto error;

	buff[3] = '\0';
	if ( strcmp(buff, "ply") != 0)
		goto error;

	//Check format (let's skip it for now, assume "ascii 1.0")

	/* What this parser reads:
	 *		element vertex
	 			x, y, z
				red, green, blue
	 *		element face
				property vertex_indices
	 */

	//Read words until end of file or end of header
	while ( fscanf(f, "%s", buff) != EOF 
		&& strcmp(buff, "end_header") != 0 )
	{
		//Found comment - Skip line
		if ( strcmp(buff, "comment") == 0 ) {
			if ( fscanf(f, "%*[^\n]%*1c") == EOF) {
				goto error;
			} 
		}
		else
		//Found an element
		if ( strcmp(buff, "element") == 0 )
		{
			//Get next word
			if ( fscanf(f, "%s", buff) == EOF )
				goto error;

			//Found vertices - save number of vertices
			if ( strcmp(buff, "vertex") == 0 ) {
				if ( fscanf(f, "%u", &vertices) == EOF ) {
					goto error;
				}
			}
			else
			//Found faces - save number of faces
			if ( strcmp(buff, "face") == 0 ) {
				if ( fscanf(f, "%u", &faces) == EOF ) {
					goto error;
				}
			}
		} 
	}

	//Read vertices
	unsigned int i;
	for (i = 0; i < vertices; i++)
	{
		//New vertex
		struct dd_vec3 vertex;

		//RGB values (0-255)
		unsigned char r, g, b;

		//Read vertex and colors values
		fscanf(f, "%f %f %f %hhu %hhu %hhu",
			&vertex.x, &vertex.y, &vertex.z,
			&r, &g, &b);

		//Create color and convert 0-255 to 0.0-1.0
		struct dd_vec3 color;
		color.r = r /255.0f;
		color.g = g /255.0f;
		color.b = b /255.0f;

		//Apply mirroring (if exists)
		if ( (mirror & 1) == 1 )
			vertex.x = -vertex.x;
		if ( (mirror & 2) == 2 )
			vertex.y = -vertex.y;
		if ( (mirror & 4) == 4 )
			vertex.z = -vertex.z;

		//Add new vertex and color to index arrays
		dd_da_add(&v_pos_index, &vertex);
		dd_da_add(&v_col_index, &color );

		//Calculate boundbox (if exists)
		/*
		if (bound)
		{
			if (bound->x < vertex.x) bound->x = vertex.x;
			if (bound->y < vertex.y) bound->y = vertex.y;
			if (bound->z < vertex.z) bound->z = vertex.z;
		}
		*/
	} 

	//For each face
	for (i = 0; i < faces; i++)
	{
		//Vertices in face
		unsigned int vf;
		fscanf(f, "%u", &vf);

		//Apply first face (each face is assumed to have at least 3 vertices)
		int first, last, new;
		fscanf(f, "%d %d %d", &first, &last, &new);

		dd_da_add(&v_pos_face, ((float*) v_pos_index.array) +(first*3));
		dd_da_add(&v_pos_face, ((float*) v_pos_index.array) +(last*3));
		dd_da_add(&v_pos_face, ((float*) v_pos_index.array) +(new*3));

		dd_da_add(&v_col_face, ((float*) v_col_index.array) +(first*3));
		dd_da_add(&v_col_face, ((float*) v_col_index.array) +(last*3));
		dd_da_add(&v_col_face, ((float*) v_col_index.array) +(new*3));

		//For each extra vertex, add a new face
		unsigned int z;
		for (z = 3; z < vf; z++) {
			last = new;

			fscanf(f, "%d", &new);

			dd_da_add(&v_pos_face, ((float*) v_pos_index.array) +(first*3));
			dd_da_add(&v_pos_face, ((float*) v_pos_index.array) +(last*3));
			dd_da_add(&v_pos_face, ((float*) v_pos_index.array) +(new*3));

			dd_da_add(&v_col_face, ((float*) v_col_index.array) +(first*3));
			dd_da_add(&v_col_face, ((float*) v_col_index.array) +(last*3));
			dd_da_add(&v_col_face, ((float*) v_col_index.array) +(new*3));
		}
	}

	//Close file
	fclose(f);

	//Mesh to return
	m->vcount = v_pos_face.elements;
	m->ar = malloc(sizeof(void*) *2);
	((void**)m->ar)[0] = v_pos_face.array;
	((void**)m->ar)[1] = v_col_face.array;
	m->attr = malloc(sizeof(char) *3);
	m->attr[0] = DD_MESH_POS;
	m->attr[1] = DD_MESH_COL;
	m->attr[2] = 0;

	//dd_mesh_to_vbo(m);

	//De-allocate
	dd_da_free(&v_pos_index);
	dd_da_free(&v_col_index);

	//Success!
	return 0;

	//Error handling
	error:
	if (ferror(f)) {
		fprintf(stderr, "load_ply: error while parsing %s: %s\n", path,
			strerror(errno));
	} else
	if (feof(f)) {
		printf("load_ply: unexpected end of file on %s\n", path);
	}
	else {
		printf("load_ply: unexpected error on %s\n", path);
	}
	fclose(f);
	return -1;
}

/* Parse OBJ */
int dd_load_obj(struct dd_mesh *m, const char *path, 
	char mirror, char *attr) {

	(void) attr;

	//Variables
	struct dd_dynamic_array v_ind, v_out;

	//Init dynamic arrays
	dd_da_init(&v_ind, sizeof(struct dd_vec3));
	dd_da_init(&v_out, sizeof(struct dd_vec3));

	//File
	FILE *f = fopen(path, "r");
	if ( !f ) {
		fprintf(stderr, "load_obj: %s: failed to open file: %s\n",
			path, strerror(errno));
		goto error;
	}

	//Buffer
	char buff[1024];

	//Get the first character of each line
	while ( fscanf(f, "%s", buff) != EOF ) {
		//Vertex
		if (buff[0] == 'v') {
			//Get vertex xyz
			struct dd_vec3 v;
			if ( fscanf(f, "%f %f %f", &v.x, &v.y, &v.z) == EOF ) {
				goto error;
			}

			//Check mirroring
			if (mirror & 1) v.x = -v.x;
			if (mirror & 2) v.y = -v.y;
			if (mirror & 4) v.z = -v.z;

			//Push vertex
			dd_da_add(&v_ind, &v);

			//Skip until next line
			fscanf(f, "%*[^\n]%*1c");
		} else
		//Face
		if (buff[0] == 'f') {

			//Assumes each face has at least 3 vertices
			int base, last, new;
			fscanf(f, "%d %d %d", &base, &last, &new);

			/* every face minus one, because obj starts from 1 */
			base--;
			last--;
			new--;
			
			/* add first face (assuming each face has at least 3 vertices) */
			dd_da_add(&v_out, dd_da_get(&v_ind, base));
			dd_da_add(&v_out, dd_da_get(&v_ind, last));
			dd_da_add(&v_out, dd_da_get(&v_ind, new ));

			/* for each extra vertex, add a new face, like a fan */
			int vert_ind;
			while( fscanf(f, "%d", &vert_ind) && !feof(f) ) {
				vert_ind--;
				last = new;
				new = vert_ind;

				dd_da_add(&v_out, dd_da_get(&v_ind, base));
				dd_da_add(&v_out, dd_da_get(&v_ind, last));
				dd_da_add(&v_out, dd_da_get(&v_ind, new ));

			}
		}
		//Unsupported element
		else {
			//Skip line
			if ( fscanf(f, "%[^\n]%*1c", buff) == EOF ) {
				goto error;
			}
		}
	}

	//Close file
	fclose(f);

	//Create mesh_data
	m->vcount = v_out.elements;
	m->ar = malloc(sizeof(void*) *1);
	((void**)m->ar)[0] = v_out.array;
	m->attr = malloc(sizeof(char) *2);
	m->attr[0] = DD_MESH_POS;
	m->attr[1] = 0;

	//dd_mesh_to_vbo(m);

	return 0;

	error:
	if (ferror(f)) {
		fprintf(stderr, "load_obj: error while parsing %s: %s\n", path,
			strerror(errno));
	} else
	if (feof(f)) {
		printf("load_obj: unexpected end of file on %s\n", path);
	}
	else {
		printf("load_obj: unexpected error on %s\n", path);
	}
	fclose(f);
	return -1;
}
